


#include "Geometry.h"

#include "Ray.h"

#include <math.h>
#include <cmath>

#include "PerlinNoise.h"

#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>


inline void Geometry::drawTriangle( const Geometry::Triangle& t, 
								 const vector<Vec4>& vertices, 
								 const vector<Vec4>& vertexNormals,
								 const vector<Vec4>& UVs) const
{
	Vec4 V0 = vertices[t.m_vertex[0]]; 
	Vec4 V1 = vertices[t.m_vertex[1]]; 
	Vec4 V2 = vertices[t.m_vertex[2]]; 
	
	Vec4 N0 = vertexNormals[t.m_vertex[0]]; 
	Vec4 N1 = vertexNormals[t.m_vertex[1]]; 
	Vec4 N2 = vertexNormals[t.m_vertex[2]]; 
	
	//const Vec4& t0 = UVs[ t.m_uvs[0] ];
	//const Vec4& t1 = UVs[ t.m_uvs[1] ];
	//const Vec4& t2 = UVs[ t.m_uvs[2] ];
	
	//glNormal3fv(&N0[0]);
	//glTexCoord2fv(&t0[0]);
	glVertex3fv(&V0[0]);
	
	//glNormal3fv(&N1[0]);
	//glTexCoord2fv(&t1[0]);
	glVertex3fv(&V1[0]);
	
	//glNormal3fv(&N2[0]);
	//glTexCoord2fv(&t2[0]);
	glVertex3fv(&V2[0]);
}


void Geometry::compileDisplayList(bool wireframe)
{
	glNewList( m_displayList, GL_COMPILE );
	
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glDisable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	
	Vec4 color(1,1,1,1);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, &color[0]);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, &color[0]);
	
	if (wireframe)
	{
		glDisable(GL_LIGHTING);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
	else
	{
		glEnable(GL_LIGHTING);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	
	glEnable(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset(1.0, 1.0);
		
	glBegin(GL_TRIANGLES);
	for (int ti=0; ti<m_triangles.size(); ++ti)
	{
		drawTriangle(m_triangles[ti], m_vertices, m_vertexNormals, m_UVs);
	}
	glEnd();
	
	glDisable(GL_POLYGON_OFFSET_FILL);
	
	glPopAttrib();
	glEndList();
	
	m_builtList = true;
}


void Geometry::draw(bool wireframe)
{
	if ( !m_builtList || !glIsList(m_builtList) )
	{
		compileDisplayList(wireframe);
	}
	
	//glPushMatrix();
	//glMultMatrixf( (GLfloat*) &m_worldFromLocal.m_matrix[0] );
	
	glCallList(m_displayList);
	
	//glPopMatrix();
}



void Geometry::setTransform(const Matrix4& worldFromLocal)
{
	m_worldFromLocal = worldFromLocal;
	
	m_invTranspose = worldFromLocal;
	m_invTranspose.invert();
	m_invTranspose.tranpose();
}

Aabb Geometry::computeAabb() const
{
	Aabb aabb;
	for (int ti=0; ti<m_triangles.size(); ++ti)
	{
		aabb.add(getTriangleAabb(ti));
	}
	return aabb;
}


Aabb Geometry::getTriangleAabb(int triIndex) const
{
	Aabb aabb;
	
	const Geometry::Triangle& t = m_triangles[triIndex];
	
	Vec4 V0 = m_vertices[t.m_vertex[0]]; V0[3] = 1.f; V0 = m_worldFromLocal * V0;
	Vec4 V1 = m_vertices[t.m_vertex[1]]; V1[3] = 1.f; V1 = m_worldFromLocal * V1;
	Vec4 V2 = m_vertices[t.m_vertex[2]]; V2[3] = 1.f; V2 = m_worldFromLocal * V2;

	aabb.includePoint(V0);
	aabb.includePoint(V1);
	aabb.includePoint(V2);
	
	return aabb;
}

void Geometry::computeSmoothedNormals()
{
	Vec4 zeroVec; zeroVec.setZero4();
	m_smoothedNormals.resize(m_vertices.size(), zeroVec);
	
	for (int ti=0; ti<m_triangles.size(); ++ti)
	{
		Geometry::Triangle& triangle = m_triangles[ti];

		const Vec4& A = m_vertices[triangle.m_vertex[0]];
		const Vec4& B = m_vertices[triangle.m_vertex[1]];
		const Vec4& C = m_vertices[triangle.m_vertex[2]];
		
		Vec4 N = (B-A) % (C-A); // Assumed winding may be wrong, will fix shortly if so
		N.normalizeIfNotZero();
		
		// Weight normals by angle subtended at triangle vertex.
		Vec4 BA = B-A;
		Vec4 CA = C-A;
		float angleA = acos(BA * CA / (BA.length3() * CA.length3()));
		
		Vec4 AB = A-B;
		Vec4 CB = C-B;
		float angleB = acos(AB * CB / (AB.length3() * CB.length3()));
		
		Vec4 AC = A-C;
		Vec4 BC = B-C;
		float angleC = acos(AC * BC / (AC.length3() * BC.length3()));
		
		m_smoothedNormals[triangle.m_vertex[0]] += angleA * N;
		m_smoothedNormals[triangle.m_vertex[1]] += angleB * N;
		m_smoothedNormals[triangle.m_vertex[2]] += angleC * N;
	}
	
	for (int vi=0; vi<m_smoothedNormals.size(); ++vi)
	{
		Vec4& smoothedN = m_smoothedNormals[vi];
		smoothedN.normalizeIfNotZero();
	}
	
	// May need to flip smoothed normals if our assumed winding was wrong (i.e. conflicts with mesh normals).
	// Flip according to majority vote of triangles (normally a global flip will be OK, if winding consistent).
	float flip = 0.0f;
	
	for (int ti=0; ti<m_triangles.size(); ++ti)
	{
		Geometry::Triangle& triangle = m_triangles[ti];

		const Vec4& NA = m_vertexNormals[triangle.m_vertexNormal[0]];
		const Vec4& NB = m_vertexNormals[triangle.m_vertexNormal[1]];
		const Vec4& NC = m_vertexNormals[triangle.m_vertexNormal[2]];
		
		const Vec4& NAs = m_smoothedNormals[triangle.m_vertex[0]];
		const Vec4& NBs = m_smoothedNormals[triangle.m_vertex[1]];
		const Vec4& NCs = m_smoothedNormals[triangle.m_vertex[2]];

		if ((NA + NB + NC)*(NAs + NBs + NCs) < 0.0f) flip -= 1.f;
	}
	
	if (flip < 0.f)
	{
		for (int vi=0; vi<m_vertices.size(); ++vi)
		{
			Vec4& smoothedN = m_smoothedNormals[vi];
			smoothedN *= -1.f;
		}
	}
}


HitData Geometry::intersect(int triIndex, Ray& worldRay) const
{
	const float m_epsilon = 1.0e-6f;
	
	Vec4& n_obj = worldRay.m_direction;
	Vec4& o_obj = worldRay.m_origin;
	
	Vec4 v0 = m_vertices[ m_triangles[triIndex].m_vertex[0] ]; v0[3] = 1.f; v0 = m_worldFromLocal * v0;
	Vec4 v1 = m_vertices[ m_triangles[triIndex].m_vertex[1] ]; v1[3] = 1.f; v1 = m_worldFromLocal * v1;
	Vec4 v2 = m_vertices[ m_triangles[triIndex].m_vertex[2] ]; v2[3] = 1.f; v2 = m_worldFromLocal * v2;
	
	Vec4 n0 = m_vertexNormals[ m_triangles[triIndex].m_vertexNormal[0] ]; n0[3] = 0.f; n0 = m_invTranspose * n0;
	Vec4 n1 = m_vertexNormals[ m_triangles[triIndex].m_vertexNormal[1] ]; n1[3] = 0.f; n1 = m_invTranspose * n1;
	Vec4 n2 = m_vertexNormals[ m_triangles[triIndex].m_vertexNormal[2] ]; n2[3] = 0.f; n2 = m_invTranspose * n2;
	
	Vec4 edge1 = v1 - v0;
	Vec4 edge2 = v2 - v0;
	Vec4 pvec = n_obj % edge2;
	
	Vec4 qvec, tvec;
	
	// Begin calculating determinant - also used to calculate U parameter.
	// if determinant is near zero, ray lies in plane of triangle.
	const float det = edge1 * pvec;
	float inv_det;
	if ( fabs(det) < 1.0e-6f ) goto noHit;

	/* Now we can safely calculate this */
	inv_det = 1.0f / det;
	
	/* calculate distance from m_v0 to ray origin */
	tvec = o_obj - v0;
	
	float u, v, w, t_hit;

	/* calculate U barycoordinate and test bounds */
	u = tvec * pvec * inv_det;
	
	if (u < 0.0f || u > 1.0f) goto noHit;
	{
		worldRay.m_hitWorld.m_intersect = false;
		return worldRay.m_hitWorld;
	}
	
	/* prepare to test V barycoordinate */
	qvec = tvec % edge1;
	
	/* calculate V barycoordinate and test bounds */
	v = n_obj * qvec * inv_det;
	if (v < 0.0f || (u + v) > 1.0f) goto noHit;

	/* calculate t_hit */
	t_hit = edge2 * qvec * inv_det;
	if ( t_hit < 5.0f*m_epsilon ) goto noHit;
	
	w = 1.0f - u - v;
	
	worldRay.m_hitWorld.m_intersect = true;
	worldRay.m_hitWorld.m_t = t_hit;
	worldRay.m_hitWorld.m_intersectionPt = o_obj + (n_obj * t_hit);
	worldRay.m_hitWorld.m_normal = w * n0 + u * n1 + v * n2;
	
	return worldRay.m_hitWorld;
	
noHit:
	
	worldRay.m_hitWorld.m_intersect = false;
	return worldRay.m_hitWorld;
	
}


void Geometry::setVertexWeightFromPerlinNoise(float scale, float alpha, float beta, int n)
{
	m_rgbvertexWeight.resize(m_vertices.size());
	
	Aabb aabb = computeAabb();
	PerlinNoise::init();
	
	for (int vi=0; vi<m_vertices.size(); ++vi)
	{
		Vec4 P = m_vertices[vi];
		
		float x = P[0]/scale;
		float y = P[1]/scale;
		float z = P[2]/scale;
		
		float noise = (float)PerlinNoise::PerlinNoise3D(x, y, z, alpha, beta, n);
		
		Vec4 N(noise, noise, noise);
		m_rgbvertexWeight[vi] = N;
	}
	
	// rescale into [0,1
	float min = 1.0e10f;
	float max = -1.0e10f;
	
	for (int vi=0; vi<m_vertices.size(); ++vi)
	{
		if (m_rgbvertexWeight[vi][0] > max) max = m_rgbvertexWeight[vi][0];
		if (m_rgbvertexWeight[vi][0] < min) min = m_rgbvertexWeight[vi][0];
	}
	
	float diff = max - min;
	float invDiff = 1.f/diff;
	for (int vi=0; vi<m_vertices.size(); ++vi)
	{
		m_rgbvertexWeight[vi][0] = (m_rgbvertexWeight[vi][0] - min) * invDiff;
		m_rgbvertexWeight[vi][1] = (m_rgbvertexWeight[vi][1] - min) * invDiff;
		m_rgbvertexWeight[vi][2] = (m_rgbvertexWeight[vi][2] - min) * invDiff;
	}	
}


void Geometry::setVertexWeightFromTexture(Texture* texture)
{
	m_rgbvertexWeight.resize(m_vertices.size());
	
	for (int ti=0; ti<m_triangles.size(); ++ti)
	{
		Geometry::Triangle& triangle = m_triangles[ti];

		for (int a=0; a<3; ++a)
		{
			Vec4& uv = m_UVs[triangle.m_uvs[a]];
			Vec4& rgb = m_rgbvertexWeight[triangle.m_vertex[a]];
			
			// Get color from texture map at (u,v) = (uv[0], uv[1])
			rgb = texture->getRGBatUV(uv[0], uv[1]);
		}
	}
	
}









